#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "shader.h"
#include "texture.h"

#include "gcmesh.h"
#include "config.h"
#include "shader_model.h"
//#include "controller.h"
//#include "glview.h"

#define GLFLOAT(VAL) VAL
typedef double value_type; 
typedef math::HierarchyMesh<math::hierarchy_mesh_traits<double, 3> >   mesh_type ;
typedef math::hierarchy_mesh_traits <double, 3>::FaceType            face_type ;
typedef math::Sphere<value_type, 3>                          sphere_type ;
typedef math::Polygon<value_type, 3>                         polygon_type ;
typedef math::vector<value_type, 3>                          point3 ;
typedef math::vector<value_type, 2>                          point2 ;
typedef math::vector<value_type, 3>                          vector3 ;
typedef math::vector<value_type, 2>                          vector2 ;

mesh_type mesh;
 
Shader shader; 
const unsigned height = 4096 ; // Proper texture size ;
const unsigned width = 2248;   // Proper texture size ; // Support resolution up to 64 

int WINDOW_WIDTH = 900, WINDOW_HEIGHT = 600;

// mouse state
int prevX = 0, prevY = 0;
bool leftPressed = false, rightPressed = false, middlePressed = false;

// view state
float rotMat [ 16 ] = {1,0,0,0,
					   0,1,0,0,
					   0,0,1,0,
					   0,0,0,1};
float focus [ 3 ] = {0,0,-1.5};
bool gb_number = false; 
bool gb_frame = true; 

Texture texture (height, width);
// This texture memory is used to update the texture.  


Face * face ;
void prepare_texture(void)
{
/*
	Point point[3];
	point[0].x = -1.0; point[0].y = -1.0; point[0].z = 0.0 ; 
	point[1].x = 1.0; point[1].y = -1.0; point[1].z = 0.0 ;
	point[2].x = -2.0; point[2].y = 1.0; point[2].z = 0.0 ; 
        
	face = new Face (texture, 0, point, 0);
	face->write_tex_param (); 
*/

}

void draw_text(GLfloat x, GLfloat y, GLfloat z,  char *text)
{
    char *p = text; 
    glPushMatrix();
    glTranslatef(x, y, z);
	double scale = 0.0002f;
	glScalef(scale, scale, scale);
	//glScalef(0.0001f, 0.0001f, 0.0001f);
    for (p = text; *p; p++)
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
    glPopMatrix();
}

void draw_number (GLfloat x, GLfloat y, GLfloat z,  int num)
{
	char array[255] ;
	sprintf (array, "%d", num);
	draw_text (x, y, z, array); 
}

void init(void)
{    
	glClearColor (0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);
	glEnable(GL_DEPTH_TEST);

	texture.init();
	//prepare_texture();

	for (unsigned i = 0; i < mesh.faces.size(); ++i)
	{
		mesh.faces[i]->set_texture (&texture ); 
		mesh.faces[i]->set_memid(i );
		mesh.faces[i]->paint_face_texture(); 
	}
}

void draw_mesh_facet (mesh_type * mesh)
{
	for (unsigned i = 0; i < mesh->faces.size(); ++i)
	{

		std::vector<point3 *> point ;
		mesh->faces[i]->Face2VertexCoord(point); 
		point3 p ;
		for (unsigned j = 0; j < point.size(); ++j)
		{
			p += (*point[j]) ;
		}
		p /= value_type(point.size());
		if (gb_number)
		{
			draw_number (p[0], p[1], p[2], i);
		}

		mesh->faces[i]->draw_flat(); 
	}	
}


void draw ()
{
	/*
	glBegin(GL_QUADS);

	glTexCoord2f(0.0, 0.0); glVertex3f(-3.0, -2.0, 0.0);
	glTexCoord2f(0.0, 1.0); glVertex3f(-3.0, 1.0, 0.0);
	glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
	glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -2.0, 0.0);

	glEnd();
	*/

	draw_mesh_facet (&mesh);

	glBegin(GL_TRIANGLES);
	//face->draw (); 
	glEnd(); 


}
void display(void)
{
	/*
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
	//glBindTexture(GL_TEXTURE_2D, texName);

	draw(); 
	glFlush();
	glDisable(GL_TEXTURE_2D);
	*/

	if (gb_frame)
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	else
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	}
	shader.set_uniform("draw_face", 1); 
	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
	// Set up viewing matrices
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60, float(WINDOW_WIDTH)/WINDOW_HEIGHT, .0001, 100);
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
	glPushMatrix();


	//Camera
	glTranslatef(focus[0], focus[1], focus[2]);
	glMultMatrixf(rotMat);

	//glRotatef(-90, 1, 0, 0);

	// drawing code goes here
	//glutSolidCube ( 1 );

	draw(); 
	glPopMatrix();
	glFlush ();

	glutSwapBuffers();
	glDisable(GL_TEXTURE_2D);
	shader.set_uniform("draw_face", 0); 
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 30.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, -3.6);
}

void keyboard (unsigned char key, int x, int y)
{
	switch (key) {
	case 'b': case 'B': gb_number = !gb_number ;
		break; 
	case 'f': case 'F': gb_frame = !gb_frame ;
		break; 

	case 27:
		exit(0);
		break;
	default:
		break;
	}
	glutPostRedisplay();
}
void motion(int x, int y)
{
	y = WINDOW_HEIGHT - y;

	float dx = (x - prevX);
	float dy = (y - prevY);

	// rotate the scene
	if (leftPressed)
	{
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glRotatef(dx, 0, 1, 0);
		glRotatef(dy, -1, 0, 0);
		glMultMatrixf(rotMat);
		glGetFloatv(GL_MODELVIEW_MATRIX, rotMat);
	}
	else if (middlePressed)
	{
		focus [ 0 ] += 0.005 * dx;
		focus [ 1 ] += 0.005 * dy;
	}
	else if (rightPressed)
	{
		focus [ 2 ] += 0.01 * dy;
	}
	
	// Store previous mouse positions
	prevX = x;
	prevY = y;

	glutPostRedisplay();
}

void mouse(int button, int state, int x, int y)
{
	y = WINDOW_HEIGHT - y;

	// Mouse state that should always be stored on pressing
	if (state == GLUT_DOWN)
	{
		prevX = x;
		prevY = y;
	}

	if (button == GLUT_LEFT_BUTTON)
	{
		leftPressed = state == GLUT_DOWN;
	}

	if (button == GLUT_RIGHT_BUTTON)
	{
		rightPressed = state == GLUT_DOWN;
	}
	
	if (button == GLUT_MIDDLE_BUTTON)
	{
		middlePressed = state == GLUT_DOWN;
	}
}

int main(int argc, char** argv)
{


	std::cin>>mesh ;
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(900, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	init();

	shader.init(); 
	//shader.load("../glsl/texture.vert", "../glsl/texture.frag");
	shader.load("../glsl/pointlight.vert","../glsl/pointlight.frag");
	shader.bind(); 


	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutMainLoop();
	return 0; 
}
